package com.jsftoolkit.gen;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.faces.component.UIComponent;
import javax.faces.webapp.UIComponentELTag;

import com.jsftoolkit.gen.info.ComponentInfo;
import com.jsftoolkit.gen.info.PropertyInfo;
import com.jsftoolkit.gen.info.TagInfo;
import com.jsftoolkit.gen.writer.TagWriter;

public class TagGeneratorImpl extends
		AbstractComponentClassGenerator<TagWriter> {

	public void generate(ComponentInfo info, TagWriter writer) {
		TagInfo tagInfo = info.getTag();

		Map<String, PropertyInfo> properties = new HashMap<String, PropertyInfo>(
				info.getProperties());
		Set<String> other = info.getRenderer().getAttribs();
		// make sure partialTriggers is handled
		other.add("partialTriggers");

		Class<? extends UIComponentELTag> superClass = tagInfo.getSuperClass();

		// print everything up to the open '{'
		printPackageAndImports(writer, tagInfo, superClass);

		if (info.getPackage() != null && info.getClassName() != null) {
			writer.printComponentImport(info.getPackage(), info.getClassName());
		}

		printDeclaration(writer, info.getTag(), superClass);

		// need a member value expression for each property.
		try {
			Set<String> names = getAllProperties(properties, other, info
					.getSuperClass(), info.getTag().getSuperClass());
			for (String attrib : names) {
				writer.printAttributeField(attrib);
			}
		} catch (IntrospectionException e) {
			throw new RuntimeException(e);
		}

		// implement setProperties
		writer.startSetProperties();

		// write the component backed properties
		for (Entry<String, PropertyInfo> prop : properties.entrySet()) {
			writer.printPropertySet(prop.getValue().getName(), prop.getKey());
		}

		// write the render time only properties
		for (String attrib : other) {
			writer.printPropertySet(attrib, null);
		}

		// end of setProperties
		writer.endBlock();

		// create the getters and setters
		writer.printAccessors();

		// write the getter for component type and default renderer
		writer.printTypeAndRendererGetters(info.getType(), info
				.getRendererType());

		writer.close();
	}

	/**
	 * 
	 * @param properties
	 *            the already registered properties. Will not be modified.
	 * @param other
	 *            other known property names not in properties.
	 * @param superClass
	 *            the component super class (cannot be null)
	 * @param superTag
	 *            the tag super class (cannot be null)
	 * @return the names of all the properties this tag handler needs to have a
	 *         get/set pair for.
	 * @throws IntrospectionException
	 */
	protected Set<String> getAllProperties(
			Map<String, PropertyInfo> properties, Set<String> other,
			Class<? extends UIComponent> superClass,
			Class<? extends UIComponentELTag> superTag)
			throws IntrospectionException {
		Set<String> set = new LinkedHashSet<String>();
		for (PropertyInfo info : properties.values()) {
			set.add(info.getName());
		}
		set.addAll(other);

		// add superclass properties that are not set by the super tag handler
		Set<String> handled = new LinkedHashSet<String>();
		for (PropertyDescriptor pd : Introspector.getBeanInfo(superTag)
				.getPropertyDescriptors()) {
			handled.add(pd.getName());
		}
		// TODO make 'transient' be ignored
		for (PropertyDescriptor pd : Introspector.getBeanInfo(superClass,
				UIComponent.class).getPropertyDescriptors()) {
			String name = pd.getName();
			if (pd.getReadMethod() != null && pd.getWriteMethod() != null
					&& !handled.contains(name)) {
				set.add(name);
			}
		}

		return set;
	}

}
